﻿/**
 * @file log.c
 * @date 23.5.7
 * @author yrz
 * @brief 日志实现，将文件的修改写入磁盘
 * @version 0.1
 * @status Stable

**/

#include "types.h"
#include "riscv.h"
#include "defs.h"
#include "param.h"
//#include "spinlock.h"
//#include "sleeplock.h"
#include "fs.h"
#include "buf.h"


//
// 日志事务包含多个FS系统调用的更新。日志系统只在没有活动
// 的FS系统调用时提交。因此，对于提交是否可能将未提交的系
// 统调用的更新写入磁盘，不需要任何推理。

// 系统调用应该调用begin_op() / end_op()来标记它的开始和
// 结束。通常，begin_op()只是增加正在进行的FS系统调用和返
// 回的计数。但是如果它认为日志即将用完，它就会休眠，直到
// 最后一个未完成的end_op()提交。
// 


// 该日志是包含磁盘块的物理re-do日志。
// 日志格式:
//   头部块,包含块A、B、C的块编号 
//   块 A
//   块 B
//   块 C
//   ...
// 日志增补是同步的

// 头块的内容，既用于磁盘上的头块，也用于在提交之前在内存中跟踪记录的块。
struct logheader {
    int n;
    int block[LOGSIZE];
};

struct log {
    //struct spinlock lock;
    int start;
    int size;
    int outstanding; // 有多少FS系统调用在执行
    int committing;  // 处于commit()中, 需要等待
    int dev;
    struct logheader lh;
};
struct log log;


static void recover_from_log(void);
static void commit();

/**
 * @brief 初始化日志
 * @param dev 设备号
 * @param sb 超级块指针
*/
void
initlog(int dev, struct superblock* sb)
{
    if (sizeof(struct logheader) >= BSIZE)
        panic("initlog: too big logheader");

    //initlock(&log.lock, "log");
    log.start = sb->logstart;
    log.size = sb->nlog;
    log.dev = dev;
    recover_from_log();
}


/**
 * @brief 将提交的块从日志复制到它们的原位置
 * @param recovering 需要恢复

*/
static void
install_trans(int recovering)
{
    int tail;

    for (tail = 0; tail < log.lh.n; tail++) {
        struct buf* lbuf = bread(log.dev, log.start + tail + 1); // 读取日志块
        struct buf* dbuf = bread(log.dev, log.lh.block[tail]); // 读目标块
        memmove(dbuf->data, lbuf->data, BSIZE);  // 将日志块拷贝到目标块
        bwrite(dbuf);  // 将目标块写入磁盘
        if (recovering == 0)
            bunpin(dbuf);
        brelse(lbuf);
        brelse(dbuf);
    }
}


/**
 * @brief 读日志块的头

*/
static void
read_head(void)
{
    struct buf* buf = bread(log.dev, log.start);
    struct logheader* lh = (struct logheader*)(buf->data);
    int i;
    log.lh.n = lh->n;
    for (i = 0; i < log.lh.n; i++) {
        log.lh.block[i] = lh->block[i];
    }
    brelse(buf);
}


/**
 * @brief 将日志头写入磁盘

*/
static void
write_head(void)
{
    struct buf* buf = bread(log.dev, log.start);
    struct logheader* hb = (struct logheader*)(buf->data);
    int i;
    hb->n = log.lh.n;
    for (i = 0; i < log.lh.n; i++) {
        hb->block[i] = log.lh.block[i];
    }
    bwrite(buf);
    brelse(buf);
}

/**
 * @brief 通过日志恢复之前内容
 
*/
static void
recover_from_log(void)
{
    read_head();
    install_trans(1); // 如果提交了，将log拷贝到磁盘
    log.lh.n = 0;
    write_head(); // 清空log
}


/**
 * @brief 系统调用时先调用该函数

*/
void
begin_op(void)
{
    //acquire(&log.lock);
    while (1) {
        if (log.committing) {
            sleep(&log);
        }
        else if (log.lh.n + (log.outstanding + 1) * MAXOPBLOCKS > LOGSIZE) {
            // this op might exhaust log space; wait for commit.
            sleep(&log);
        }
        else {
            log.outstanding += 1;
            //release(&log.lock);
            break;
        }
    }
}


/**
 * @brief 系统调用结束前调用该函数。如果这是最后一个FS系统调用操作，提交。

*/
void
end_op(void)
{
    int do_commit = 0;

    // acquire(&log.lock);
    log.outstanding -= 1;
    if (log.committing)
        panic("log.committing");
    if (log.outstanding == 0) {
        do_commit = 1;
        log.committing = 1;
    }
    else {
        
        wakeup(&log);
    }
    // release(&log.lock);

    if (do_commit) {
        
        commit();
        //acquire(&log.lock);
        log.committing = 0;
        wakeup(&log);
        //release(&log.lock);
    }
}


/**
 * @brief 将修改的块从缓存拷贝到日志中

*/
static void
write_log(void)
{
    int tail;

    for (tail = 0; tail < log.lh.n; tail++) {
        struct buf* to = bread(log.dev, log.start + tail + 1); // 日志块
        struct buf* from = bread(log.dev, log.lh.block[tail]); // 缓存块
        memmove(to->data, from->data, BSIZE);
        bwrite(to);  // 写日志
        brelse(from);
        brelse(to);
    }
}

static void
commit()
{
    if (log.lh.n > 0) {
        write_log();     // 将修改块写入日志
        write_head();    // 日志头写入磁盘Write header to disk -- the real commit
        install_trans(0); // 写入原位置
        log.lh.n = 0;
        write_head();    // 从日志中删除事务
    }
}


/**
 * @brief 写日志

*/
void
log_write(struct buf* b)
{
    int i;

    //acquire(&log.lock);
    if (log.lh.n >= LOGSIZE || log.lh.n >= log.size - 1)
        panic("too big a transaction");
    if (log.outstanding < 1)
        panic("log_write outside of trans");

    for (i = 0; i < log.lh.n; i++) {
        if (log.lh.block[i] == b->blockno)   // log absorption
            break;
    }
    log.lh.block[i] = b->blockno;
    if (i == log.lh.n) {  // 在日志中添加新块
        bpin(b);
        log.lh.n++;
    }
    //release(&log.lock);
}

